package io.github.kuafucv.zero.log.core;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.ThrowableProxy;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.encoder.Encoder;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.kuafucv.zero.log.core.helper.system.HostInfo;
import io.github.kuafucv.zero.log.core.http.HttpBuilder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public abstract class AbstractHttpAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    private String endpointUrl;
    private int httpReadTimeoutInMillis = 1000;
    private int httpConnectionTimeoutInMillis = 1000;
    private String appName;
    private int appServerPort;
    private String env;
    private boolean includeMDC = true;
    private Encoder<ILoggingEvent> encoder;


    /** ------ 内部自动初始化 ------ */
    private static final ObjectMapper objectMapper = new ObjectMapper();



    @Override
    public void start() {
        if (this.endpointUrl == null || this.endpointUrl.trim().isEmpty()) {
            addError("endpointUrl must be configured");
            return;
        }
        if(this.appName == null || this.appName.trim().isEmpty()) {
            addError("appName must be configured");
            return;
        }
        if(this.env == null || this.env.trim().isEmpty()) {
            this.env = "env";
        }
        HttpBuilder.setHttpConnectionTimeoutInMillis(getHttpConnectionTimeoutInMillis());
        HttpBuilder.setHttpReadTimeoutInMillis(getHttpReadTimeoutInMillis());
        HttpBuilder.setUrl(endpointUrl);
        super.start();
    }

    /**
     * {
     *                         "trace_id":"%X{TRACE_ID}",
     *                         "timestamp": "%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ssXXX}}",
     *                         "level": "${LOG_LEVEL_PATTERN:-%5p}",
     *                         "pid": "${PID:-}",
     *                         "app": "${app_name}",
     *                         "env": "${env}",
     *                         "thread": "%thread",
     *                         "class": "%logger",
     *                         "method": "%method",
     *                         "message": "%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}",
     *                         "statck_trace":"%xEx"
     *                         }
     */
    protected String convertEventToJson(ILoggingEvent event) throws JsonProcessingException {
        Map<String, Object> logMap = new HashMap<>();

        try{
            logMap.put("hostname", HostInfo.buildInstance().getName());
            logMap.put("ip", HostInfo.buildInstance().getAddress());
        }catch (Exception ignored){}

        try{
            logMap.put("port", this.appServerPort);
        }catch (Exception ignored){}

        String patternLog = new String(this.encoder.encode(event), StandardCharsets.UTF_8);
        logMap.put("patternLog", patternLog);
        // 基础日志信息
        logMap.put("timestamp", event.getTimeStamp());
        logMap.put("level", event.getLevel().toString());
        logMap.put("pid", Thread.currentThread().getId());
        logMap.put("app", appName);
        logMap.put("env", env);
        logMap.put("thread", event.getThreadName());
        logMap.put("logger", event.getLoggerName());
        logMap.put("message", event.getFormattedMessage());
        // MDC上下文信息
        if (includeMDC && event.getMDCPropertyMap() != null && !event.getMDCPropertyMap().isEmpty()) {
            logMap.put("mdc", event.getMDCPropertyMap());
        }

        // 异常堆栈
        if (event.getThrowableProxy() != null) {
            ThrowableProxy throwableProxy = (ThrowableProxy) event.getThrowableProxy();
            Map<String, Object> exceptionMap = new HashMap<>();
            exceptionMap.put("className", throwableProxy.getClassName());
            exceptionMap.put("message", throwableProxy.getMessage());
            exceptionMap.put("stackTrace", throwableProxy.getStackTraceElementProxyArray());
            logMap.put("exception", exceptionMap);
        }

        // 调用方信息（可选，性能敏感）
        if (event.hasCallerData()) {
            StackTraceElement[] callerData = event.getCallerData();
            if (callerData != null && callerData.length > 0) {
                StackTraceElement firstCaller = callerData[0];
                Map<String, Object> callerMap = new HashMap<>();
                callerMap.put("class", firstCaller.getClassName());
                callerMap.put("method", firstCaller.getMethodName());
                callerMap.put("file", firstCaller.getFileName());
                callerMap.put("line", firstCaller.getLineNumber());
                logMap.put("caller", callerMap);
            }
        }
        return objectMapper.writeValueAsString(logMap);
    }

    protected byte[] toBytes(final InputStream is) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        int count;
        byte[] buf = new byte[512];

        while((count = is.read(buf, 0, buf.length)) != -1) {
            outputStream.write(buf, 0, count);
        }
        outputStream.flush();
        return outputStream.toByteArray();
    }



    /** ------ get set ------*/

    public String getEndpointUrl() {
        return endpointUrl;
    }

    public void setEndpointUrl(String endpointUrl) {
        this.endpointUrl = endpointUrl;
    }

    public int getHttpReadTimeoutInMillis() {
        return httpReadTimeoutInMillis;
    }

    public void setHttpReadTimeoutInMillis(int httpReadTimeoutInMillis) {
        this.httpReadTimeoutInMillis = httpReadTimeoutInMillis;
    }

    public int getHttpConnectionTimeoutInMillis() {
        return httpConnectionTimeoutInMillis;
    }

    public void setHttpConnectionTimeoutInMillis(int httpConnectionTimeoutInMillis) {
        this.httpConnectionTimeoutInMillis = httpConnectionTimeoutInMillis;
    }

    public String getAppName() {
        return appName;
    }

    public void setAppName(String appName) {
        this.appName = appName;
    }

    public String getEnv() {
        return env;
    }

    public void setEnv(String env) {
        this.env = env;
    }

    public boolean isIncludeMDC() {
        return includeMDC;
    }

    public void setIncludeMDC(boolean includeMDC) {
        this.includeMDC = includeMDC;
    }

    public Encoder<ILoggingEvent> getEncoder() {
        return encoder;
    }

    public void setEncoder(Encoder<ILoggingEvent> encoder) {
        this.encoder = encoder;
    }

    public int getAppServerPort() {
        return appServerPort;
    }

    public void setAppServerPort(int appServerPort) {
        this.appServerPort = appServerPort;
    }
}
